#pragma once

#include <iostream>
#include <fstream>
#include <stdlib.h>
#include <boost/random.hpp>
#include <boost/filesystem.hpp>
#include "SBUN.h"
#include "DataTypes.h"

using namespace std;

class CRelation
{
public:
	CRelation() : _bat(NULL), _cardinality(0)
	{
	}

	CRelation(uint32 cardinality) : _bat(NULL), _cardinality(cardinality)
	{
		if(cardinality > 0)
		{
			_bat = (SBUN*)malloc(cardinality * sizeof(SBUN));
		}
	}

	~CRelation(void)
	{
		if(_bat != NULL)
		{
			free(_bat);
		}
	}

	void load(const char* filename, bool binary)
	{
		if(binary)
		{
			loadBinary(filename);
		}
		else
		{
			loadText(filename);
		}
	}

	void save(const char* filename, bool binary)
	{
		if(binary)
		{
			saveBinary(filename);
		}
		else
		{
			saveText(filename);
		}
	}


	void initialiseSet_Uniform(uint32 minValue, uint32 maxValue)
	{
		boost::mt19937 gen(1337);
		boost::uniform_int<uint32> dist(minValue, maxValue);
		boost::variate_generator<boost::mt19937&, boost::uniform_int<uint32> > num(gen, dist);

		SBUN* p = _bat;

		for(uint32 counter = 0; counter < _cardinality; ++counter)
		{
			p->key = num();
			p->payload = counter;

			++p;
		}
	}

	void initialiseSet_Gaussian(uint32 minValue, uint32 maxValue, uint32 mean, double thirdStandardDeviationFractionOfCardinality)
	{
		// 99.7% of population is in 3 standard deviations from mean. So, supply a fraction of total cardinality that will constitute the bulk of the bell
		// curve, and this function will sort out the rest :)

		uint32 cardinality = maxValue - minValue;
		double sigma = cardinality * thirdStandardDeviationFractionOfCardinality / 6.0;

		boost::mt19937 gen(1337);
		boost::normal_distribution<> dist(mean, sigma);
		boost::variate_generator<boost::mt19937&, boost::normal_distribution<> > num(gen, dist);

		SBUN* p = _bat;
		uint32 number;

		for(uint32 counter = 0; counter < _cardinality; ++counter)
		{
			// Get a number...
			number = (uint32)num();

			// Reject values that fall out of the allowed range (this may cause the sigma value to be inexact, but numbers outside of range are superbad!)
			while(number < minValue || number > maxValue)
			{
				number = (uint32)num();	// Silly values of minValue and maxValue will cause an infinite loop here. Don't use silly values!
			}

			p->key = number;
			p->payload = counter;

			++p;
		}
	}

	void initialiseSet_Debug(bool reverse, uint32 value)
	{
		SBUN* p = _bat;

		for(uint32 counter(0); counter < _cardinality; ++counter)
		{
			p[counter].key = reverse?(_cardinality - (counter+1)):counter;
			p[counter].payload = p[counter].key + value;
		}

		/*
		p[0].key = 9; p[0].payload = 1009;
		p[1].key = 7; p[1].payload = 1007;
		p[2].key = 5; p[2].payload = 1005;
		p[3].key = 3; p[3].payload = 1003;
		p[4].key = 1; p[4].payload = 1001;
		p[5].key = 8; p[5].payload = 1008;
		p[6].key = 6; p[6].payload = 1006;
		p[7].key = 4; p[7].payload = 1004;
		p[8].key = 2; p[8].payload = 1002;
		p[9].key = 0; p[9].payload = 1000;
		*/
	}

	__forceinline SBUN* getBATPointer() const
	{
		return _bat;
	}

	__forceinline uint32 getCardinality() const
	{

		return _cardinality;
	}

	void showDebug(char *relation)
	{				
		printf("Relation %s debug:\n", relation);

		SBUN* p = _bat;
		for(uint32 index = 0; index < _cardinality; ++index)
		{						
			printf("Key: %u Payload: %u\n", p->key, p->payload);
			++p;
		}
	}
	
private:

	void loadBinary(const char* filename)
	{
		uint64 fileSize(0);
		uint32 cardinality(0);
		ifstream file;
		fileSize = boost::filesystem::file_size(filename);

		if(fileSize != 0)
		{
			cardinality = (uint32)(fileSize / sizeof(SBUN));
			if(cardinality != 0)
			{
				_cardinality = cardinality;
				_bat = (SBUN*)malloc(cardinality * sizeof(SBUN));

				file.open(filename, std::ios::binary);
				file.read((char *)_bat, fileSize);
				file.close();
			}
		}
	}

	void loadText(const char* filename)
	{
		ifstream file(filename);		
		SBUN* p(NULL);
		char banana;

		uint32 cardinality(0);

		file >> cardinality;

		if(cardinality > 0)
		{
			_cardinality = cardinality;
			_bat = (SBUN*)malloc(cardinality * sizeof(SBUN));

			p = _bat;
			for(uint32 counter(0); counter < cardinality; ++counter)
			{
				file >> p->key;
				file >> banana;
				file >> p->payload;

				++p;
			}
		}

		file.close();
	}


	void saveBinary(const char* filename)
	{
		ofstream file(filename, std::ios::binary);
		file.write((const char *)_bat, _cardinality * sizeof(SBUN));
		file.close();
	}

	void saveText(const char* filename)
	{
		SBUN* p = _bat;

		ofstream file(filename);
		
		file << _cardinality << endl;
		for(uint32 counter = 0; counter < _cardinality; ++counter)
		{
			file << p->key << "," << p->payload << endl;
			++p;
		}

		file.close();
	}

	SBUN* _bat;
	uint32 _cardinality;
};